#ifndef _QGCM_MEXCONTAINER_
#define _QGCM_MEXCONTAINER_

#include <stdlib.h>
#include <vector>

#include "matrix.h"
#include "mat.h"

#include "datatypes.h"
#include "model.h"
#include "static.h"

#ifdef __QGCMDOUBLEPRECISION
#define ncREAL ncDouble
#else
#define ncREAL ncFloat
#endif


/* ----------------------------------------------------------------------------------------------- */
//
//  The basic mxContainer wrapper around mxArray * 
//
/* ----------------------------------------------------------------------------------------------- */
class mxContainer {
  protected:
    mxArray * mxValue;
  public:
    mxContainer();
    mxContainer(mxArray *);
    mxContainer(const mxContainer &);
    mxContainer(const parameter &);
    mxContainer(const double &);
    mxContainer(const string &);
    mxContainer(const char *);
    mxContainer(const char * stringList[]);
    template<class T, int S> mxContainer(const rank<T, S> *, const int size);
    template<class T, int S> mxContainer(const vector< rank<T, S> > &);
    ~mxContainer();
    bool operator==(const mxContainer &) const;
    bool operator==(const double) const;
    bool operator==(const char *) const;
    bool operator==(const string &) const;
    bool operator==(const parameter &) const;
    bool isEmpty() const;
    mxContainer & operator=(const mxContainer &);
    // unpacking functions        
    const mxArray * mxGetArray() const;	
    bool unpack( double & ) const;
    bool unpack( parameter & ) const;
    bool unpack( string & ) const;
    bool unpack( vector<string> & ) const;
    template<class T, int S> bool unpack( rank<T,S> * & ) const;
};




template<class T>
class mxVector : public mxContainer {
	public:
	mxVector(size_t size, const  T & initializer);
	T & operator[](size_t index);
        size_t size() const;
        void resize(size_t size, const T & initializer);
};

class mxComplexRef {
	double * const rPtr;
	double * const iPtr;
	public:
		mxComplexRef( complex<double> & );
		mxComplexRef(double * real, double * imag);
		mxComplexRef & operator=( const qgcomplex & );
		double & real();
		const double & real() const;
		double & imag();
		const double & imag() const;
		~mxComplexRef();
};

template<>
class mxVector< mxComplexRef > : public mxContainer {
	map< size_t, mxComplexRef *> refMap;
	public:
	mxVector(size_t size, const mxComplexRef & initializer);
	mxComplexRef & operator[](size_t index);
	const mxComplexRef & operator[](size_t index) const;
        size_t size() const;
        void resize(size_t size, const mxComplexRef & initializer);
};



template<class T>
class mxMatrix : public mxContainer {
 	rank<mwSize,2> dims;
	public:
	mxMatrix(size_t mSize, size_t nSize, const T & );
	mxMatrix(const rank<mwSize,2> & size);
	T & operator()(size_t i, size_t j);
	const T & operator()(size_t i, size_t j) const;
        const rank<mwSize,2> & size() const;
        size_t mSize() const;
        size_t nSize() const;
        void resize(const rank<mwSize,2> & size, const T & initializer);
};


// Interface for scalar struct object
class mxStruct : public mxContainer {
  protected:
  public:
    mxStruct();
    mxStruct(const mxStruct &);
    mxStruct(const char * fieldNames []);
    mxStruct(mxArray *);
    template <class T, int S> mxStruct( const map_container<T, S> &, const char * descr );
    bool operator==(const mxStruct &);
    mxStruct & operator=(const mxStruct &);
    void setField(const char * fieldName, const mxContainer & value);
    bool doesFieldExist( const char * fieldName ) const;
    mxContainer getField(const char * fieldName) const;
    // specialized unpacking function
    template <class T, int S> bool unpack( map_container<T, S> &, string & ) const;
  private:
    const mxArray * mxGetRawField(const char * fieldName) const;
};

class mxCellArray : public mxContainer {
	class mxContainerPointer : public mxContainer {
		public:
			mxContainerPointer( mxArray * );
			~mxContainerPointer();
	};
	vector<mxContainerPointer*> refs; 
	public: 
	mxCellArray( size_t size );	
	mxCellArray( const mxContainer &, size_t size );
	~mxCellArray();
	mxContainer & operator[](size_t index);
};

class mxCellArrayOfStructs : public mxContainer {
	/* If you add the ability to resize the container or change
           the mxStructs contained inside the container mxArray you MUST clear the ref MAP !!! 
	   otherwise it will contain stale cached pointers */
	class mxStructPointer : public mxStruct {
		public:
			mxStructPointer( mxArray * );
			~mxStructPointer( );
	};
	map< size_t, mxStructPointer* > refs; 
	public:
	mxCellArrayOfStructs( size_t size );
        void resetRefMap();
	~mxCellArrayOfStructs();
	mxStruct & operator[](size_t index);
};

class MATContainer {
	protected:
		MATFile * mfile;
	public:
		MATContainer();
		~MATContainer();
		bool isValid() const;
};

class MATReader : public MATContainer {
	public:
		MATReader(const char * fname);
		mxContainer mxGetVariable(const char *) const;
		mxStruct mxGetStruct(const char *) const;
		bool doesVariableExist(const char *) const;
		bool doesStructExist(const char *) const;
};

class MATWriter : public MATContainer {
	public:
		MATWriter(const char * fname);
		void mxWriteVariable( const mxContainer &, const char * );
};

#endif
